﻿// Partial Copyright (c) LogoUI Software Solutions LTD
// Autor: Vladislav Spivak
// This source file is the part of LogoFX Framework http://logofx.codeplex.com
// See accompanying licences and credits.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.ReflectionModel;
using System.Linq;
using System.Windows.Markup;
using Autofac;
using Autofac.Core;
using LogoFX.Composition;
using MefContrib.Hosting.Generics;
using Microsoft.ComponentModel.Composition.Hosting;
using Autofac.Integration.Mef;
using IContainer = Autofac.IContainer;

namespace Caliburn.Micro
{
    /// <summary>
    /// Autofac bootstrapper
    /// </summary>
    /// <typeparam name="T">root <c>ViewModel</c></typeparam>
    [ContentProperty("CatalogConfig")]
    public class AutoMefBootstrapper<T> : BootstrapperBase<T>
    {
 
        #region Fields

        readonly DeploymentCatalogLoader _loader = new DeploymentCatalogLoader();
        readonly DeploymentCatalog _dc = new DeploymentCatalog();
        private IContainer _baseContainer;
        //private IContainer _multitenantContainer;
        private readonly ILog _logger = LogManager.GetLog(typeof(AutoMefBootstrapper<>));        
        //private  ContainerBuilder _containerBuilder = new ContainerBuilder();
        #endregion
      
        #region Properties

        /// <summary>
        /// Gets or sets the catalog configuration.
        /// </summary>
        /// <value>The catalog configuration.</value>
        public DeploymentCatalogConfig CatalogConfig
        {
            get { return _loader.CatalogConfig; }
            set { _loader.CatalogConfig = value; }
        }

        /// <summary>
        /// <see cref="Autofac"/> modules list
        /// </summary>
        [ImportMany(typeof(IModule))]
        public IModule[] Modules { get; set; }

        //autofac container

        /// <summary>
        /// Gets the container.
        /// </summary>
        /// <value>The container.</value>
        protected IContainer Container
        {
            get { return _baseContainer; }
        }
        #endregion

        /// <summary>
        /// Override to configure the framework and setup your <c>IoC</c> container.
        /// </summary>
        protected override void Configure()
        {
            //configure mef
            _loader.CatalogCached += (unused, args) => args.Catalog.Parts
                                                         .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
                                                         .Where(assembly => !AssemblySource.Instance.Contains(assembly))
                                                         .Apply(a => AssemblySource.Instance.Add(a));

            CompositionHostEx.Initialize(_loader.LoadCatalogConfigAsync(new GenericCatalog(_dc, new GenericContractRegistry())));

            ContainerBuilder cb = new ContainerBuilder();
            OnGlobalConfigure(cb);
            //  register the single window manager for this container
            cb.Register<IWindowManager>(c => new WindowManager()).SingleInstance();
            //  register the single event aggregator for this container
            cb.Register<IEventAggregator>(c => new EventAggregator()).SingleInstance();
            // self
            cb.RegisterType<T>().AsSelf().SingleInstance();

           _baseContainer = cb.Build();
        }


        /// <summary>
        /// Override this to add custom behavior to execute after the application starts.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The arguments.</param>
        protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
        {
            if (CatalogConfig != null)
            {
                EventHandler loadAction = null;
                loadAction = (s, a) =>
                {
                    _loader.ConfigLoaded -= loadAction;
                    AutofacContainerRegister();
                    Execute.OnUIThread(DisplayRootView);
                    ((INotifyComposablePartCatalogChanged)CompositionHostEx.GetContainer().Catalog).Changed += (f, b) => UpdateMefRegistration();
                };

                _loader.ConfigLoaded += loadAction;
                DeploymentCatalogLoader.ConfigLoadHelper loadHelper = new DeploymentCatalogLoader.ConfigLoadHelper(CatalogConfig);
                loadHelper.LoadAsync(_loader.OnLoaded, _loader.OnGroupLoaded, _loader.OnItemLoaded);
            }
            else
            {
                AutofacContainerRegister();
                ((INotifyComposablePartCatalogChanged)CompositionHostEx.GetContainer().Catalog).Changed += (f, b) => UpdateMefRegistration();
                base.OnStartup(sender, e);
            }
        }

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation.
        /// </summary>
        /// <param name="serviceType">The service to locate.</param>
        /// <param name="key">The key to locate.</param>
        /// <returns>The located service.</returns>
        protected override object GetInstance(Type serviceType, string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                if (_baseContainer.IsRegistered(serviceType))
                    return _baseContainer.Resolve(serviceType);
            }
            else
            {
                if (_baseContainer.IsRegisteredWithName(key, serviceType))
                    return _baseContainer.ResolveNamed(key, serviceType);
            }
            throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? serviceType.Name));
        }

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation
        /// </summary>
        /// <param name="serviceType">The service to locate.</param>
        /// <returns>The located services.</returns>
        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return _baseContainer.Resolve(typeof(IEnumerable<>).MakeGenericType(serviceType)) as IEnumerable<object>;
        }

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation.
        /// </summary>
        /// <param name="instance">The instance to perform injection on.</param>
        protected override void BuildUp(object instance)
        {
            _baseContainer.InjectProperties(instance);
        }

        /// <summary>
        /// Provides override for derived class configuration handling
        /// </summary>
        /// <param name="builder"> autofac container builder</param>
        protected virtual void OnGlobalConfigure(ContainerBuilder builder)
        {
        }

        private void UpdateMefRegistration()
        {
            ContainerBuilder cb = new ContainerBuilder();


            cb.RegisterComposablePartCatalog(CompositionHostEx.GetContainer().Catalog);

            CompositionHostEx.GetContainer().Catalog.Parts
                .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
                .Where(assembly => !AssemblySource.Instance.Contains(assembly))
                .Apply(a => AssemblySource.Instance.Add(a));

            cb.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
                //  must be a type that ends with ViewModel
              .Where(type => type != typeof(T) && type.Name.EndsWith("ViewModel"))
                //  must be in a namespace ending with ViewModels
              .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace != null && type.Namespace.EndsWith("ViewModels"))
                //  must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this)
              .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name, false) != null)
                //  registered as self
              .AsSelf()
                //  always create a new one
              .InstancePerDependency();

            cb.Update(_baseContainer);
        }
        /// <summary>
        /// Register autofac container
        /// </summary>
        private void AutofacContainerRegister()
        {
            ContainerBuilder cb =  new ContainerBuilder();

            OnConfigure(cb);

            cb.RegisterComposablePartCatalog(CompositionHostEx.GetContainer().Catalog);

            CompositionHostEx.GetContainer().SatisfyImportsOnce(this);

            if (Modules != null)
                Modules.Apply(cb.RegisterModule);

            //  register view models
            cb.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
                //  must be a type that ends with ViewModel
              .Where(type => type != typeof(T) && type.Name.EndsWith("ViewModel"))
                //  must be in a namespace ending with ViewModels
              .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace!=null && type.Namespace.EndsWith("ViewModels"))
                //  must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this)
              .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name, false) != null)
                //  registered as self
              .AsSelf()
                //  always create a new one
              .InstancePerDependency();

            ////  register views
            //cb.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
            //    //  must be a type that ends with View
            //  .Where(type => type.Name.EndsWith("View"))
            //    //  must be in a namespace that ends in Views
            //  .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace != null && type.Namespace.EndsWith("Views"))
            //    //  registered as self
            //  .AsSelf()
            //    //  always create a new one
            //  .InstancePerDependency();

            cb.Update(_baseContainer);
        }

        protected virtual void OnConfigure(ContainerBuilder containerBuilder)
        {            
        }
    }

}
